package luzhin;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import static luzhin.Piece.*;

/**
 *
 * @author lacungus
 */
public class MoveGenerator {

    private AttackDiagram diagram;
    private List<Move> moves;
	private int[] whiteAttacks;
	private int[] blackAttacks;
    private byte[] field;
    private boolean isWhiteToMove;
    private Board board;
    
    public MoveGenerator() {
    }

    public MovesAndAttacks generate(Board board) {
        moves = new LinkedList<Move>();
        
        this.board = board;
        field = board.getField();
		isWhiteToMove = board.isWhiteToMove();
        
		whiteAttacks = new int[Board.SIZE];
		blackAttacks = new int[Board.SIZE];
		Arrays.fill(whiteAttacks, 0);
		Arrays.fill(blackAttacks, 0);

		for (int i=0;i<Board.SIZE;++i)
			processPosition(i);

		boolean isSafe = true;
		int whiteKingPos = 0;
		int blackKingPos = 0;
		for (int i=0;i<Board.SIZE;++i) {
			int color = Piece.getColor(field[i]);
			if (color == 1 && whiteAttacks[i] < blackAttacks[i]) {
				isSafe = false;
			}
			if (color == -1 && blackAttacks[i] < whiteAttacks[i]) {
				isSafe = false;
			}
			if (field[i] == Piece.WK)
				whiteKingPos = i;
			if (field[i] == Piece.BK)
				blackKingPos = i;
		}
		boolean checkToWhite = false;
		boolean checkToBlack = false;
		if (blackAttacks[whiteKingPos] > 0)
			checkToWhite = true;
		if (whiteAttacks[blackKingPos] > 0)
			checkToBlack = true;
		diagram = new AttackDiagram(whiteAttacks, blackAttacks, isSafe, checkToWhite, checkToBlack);
        if (isWhiteToMove)
            addCastles(whiteKingPos);
        if (!isWhiteToMove)
            addCastles(blackKingPos);
        
        return new MovesAndAttacks(moves, diagram);
    }
    
	private void processPosition(int pos) {
		int piece = field[pos];
        boolean itsMove = itsMove(pos);
		if (piece == Piece.NONE)
			return;

        int[] dx = {};
		int[] dy = {};
		int limit = 0;

		if (piece == WQ || piece == BQ) {
			dx = Piece.dxQueen;
			dy = Piece.dyQueen;
			limit = Board.LEN;
		}
		if (piece == WR || piece == BR) {
			dx = Piece.dxRook;
			dy = Piece.dyRook;
			limit = Board.LEN;
		}
		if (piece == WN || piece == BN) {
			dx = Piece.dxKnight;
			dy = Piece.dyKnigth;
			limit = 1;
		}
		if (piece == WB || piece == BB) {
			dx = Piece.dxBishop;
			dy = Piece.dyBishop;
			limit = Board.LEN;
		}
		if (piece == WK || piece == BK) {
			dx = Piece.dxKing;
			dy = Piece.dyKing;
			limit = 1;
		}
		if (piece == WP) {
			dx = Piece.dxWhitePawn;
			dy = Piece.dyWhitePawn;
			limit = 1;

            if (itsMove)
                addPawnMoves(pos);
		}
		if (piece == BP) {
			dx = Piece.dxBlackPawn;
			dy = Piece.dyBlackPawn;
			limit = 1;
            if (itsMove)
                addPawnMoves(pos);
		}
		int i = pos / Board.LEN;
		int j = pos % Board.LEN;

        int[] attacksToIncrement = (Piece.getColor(piece) == 1) ? whiteAttacks : blackAttacks;
		for (int dir=0;dir<dx.length;++dir) {
    		for (int k=1;k<=limit;++k) {
				int x = i + k * dx[dir];
				int y = j + k * dy[dir];
				int ps = Position.getPosition(x, y);
				if (ps == Position.INVALID) {
                    break;
				}
                if (Piece.getColor(field[ps]) != Piece.getColor(piece) && itsMove && piece != WP && piece != BP) {
                    moves.add(new Move(pos, ps));
                }
				attacksToIncrement[ps]++;
				if (field[ps] != Piece.NONE) {
					break;
				}
			}
		}
	}

    private boolean itsMove(int pos) {
        return isWhiteToMove && Piece.getColor(field[pos]) == 1 || !isWhiteToMove && Piece.getColor(field[pos]) == -1;
    }

    private void addPawnMoves(int pos) {
        int piece = field[pos];
		int color = Piece.getColor(piece);
		int i = pos / Board.LEN;
		int j = pos % Board.LEN;

		if (color == 1) {
			boolean onStart = (i == 1);
			int nextPos = Position.getPosition(i + 1, j);
			if (nextPos != Position.INVALID && field[nextPos] == 0) {
				moves.add(new Move(pos, nextPos));
				nextPos =  Position.getPosition(i + 2, j);
				if (onStart && nextPos != -1 && Piece.getColor(field[nextPos]) == 0) {
					moves.add(new Move(pos, nextPos));
				}
			}
		} else {
			boolean onStart = (i == 6);
			int nextPos = Position.getPosition(i - 1, j);
			if (nextPos != Position.INVALID && field[nextPos] == 0) {
				moves.add(new Move(pos, nextPos));
				nextPos =  Position.getPosition(i - 2, j);
				if (onStart && nextPos != -1 && Piece.getColor(field[nextPos]) == 0) {
					moves.add(new Move(pos, nextPos));
				}
			}
		}
    }

	private void addCastles(int start) {
		if (canCastleShort(start)) {
			moves.add(new Move(start, start + 2));
		}
		if (canCastleLong(start)) {
			moves.add(new Move(start, start - 2));
		}
	}
	
	private boolean canCastleShort(int start) {
		boolean res = true;

		if (isWhiteToMove && (board.isWhiteKingMoved() || board.isRookH1Moved()))
				return false;
		if (!isWhiteToMove && (board.isBlackKingMoved() || board.isRookH8Moved()))
			return false;

		for (int k=1;k<=2;++k)
			if (board.getPiece(start + k) != Piece.NONE)
				res = false;

		//tests squares passed by the king for checks
		for (int k=start;k<=start+2;++k) {
			if (board.isWhiteToMove() && diagram.getBlackAttacks()[k] > 0 || !board.isWhiteToMove() && diagram.getWhiteAttacks()[k] > 0)
				res = false;
		}
		return res;
	}

	private boolean canCastleLong(int start) {
		boolean res = true;

		if (isWhiteToMove && (board.isWhiteKingMoved() || board.isRookA1Moved()))
			return false;
		if (!isWhiteToMove && (board.isBlackKingMoved() || board.isRookA8Moved()))
			return false;

		for (int k=1;k<=3;++k) {
			if (board.getPiece(start - k) != Piece.NONE)
				res = false;
		}
		//tests squares passed by the king for checks
		for (int k=start;k>=start-2;--k) {
			if (board.isWhiteToMove() && diagram.getBlackAttacks()[k] > 0 || !board.isWhiteToMove() && diagram.getWhiteAttacks()[k] > 0)
				res = false;
		}
		return res;
	}

    public static class MovesAndAttacks {
        private List<Move> moves;
        private AttackDiagram attackDiagram;

        private MovesAndAttacks(List<Move> moves, AttackDiagram attackDiagram) {
            this.moves = moves;
            this.attackDiagram = attackDiagram;
        }

        public List<Move> getMoves() {
            return moves;
        }
        
        public AttackDiagram getAttackDiagram() {
            return attackDiagram;
        }
    }
}
